D:\a\tools.proto\tools.proto\compiler\src\gen\template\core.rs
Line | Count | Source |
1 | | // Copyright (c) 2024, BlockProject 3D |
2 | | // |
3 | | // All rights reserved. |
4 | | // |
5 | | // Redistribution and use in source and binary forms, with or without modification, |
6 | | // are permitted provided that the following conditions are met: |
7 | | // |
8 | | // * Redistributions of source code must retain the above copyright notice, |
9 | | // this list of conditions and the following disclaimer. |
10 | | // * Redistributions in binary form must reproduce the above copyright notice, |
11 | | // this list of conditions and the following disclaimer in the documentation |
12 | | // and/or other materials provided with the distribution. |
13 | | // * Neither the name of BlockProject 3D nor the names of its contributors |
14 | | // may be used to endorse or promote products derived from this software |
15 | | // without specific prior written permission. |
16 | | // |
17 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
18 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
19 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
20 | | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
21 | | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
22 | | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
23 | | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
24 | | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
25 | | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
26 | | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
27 | | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | |
29 | | use crate::gen::template::options::Options; |
30 | | use crate::gen::template::parse_tree::{Component, Fragment, FragmentMode, Token}; |
31 | | use crate::gen::template::Error; |
32 | | use itertools::Itertools; |
33 | | use std::borrow::Cow; |
34 | | use std::collections::HashMap; |
35 | | use crate::gen::codec::CodecMap; |
36 | | |
37 | | pub struct Template<'fragment, 'variable> { |
38 | | fragments: HashMap<String, Fragment<'fragment>>, |
39 | | variables: HashMap<&'variable str, Cow<'variable, str>>, |
40 | | } |
41 | | |
42 | | impl<'fragment, 'variable> Template<'fragment, 'variable> { |
43 | 130 | pub fn compile(data: &'fragment [u8]) -> Result<Self, Error> { |
44 | 130 | Self::compile_with_options(data, &Options::default()) |
45 | 130 | } |
46 | | |
47 | 372 | pub fn compile_with_options(data: &'fragment [u8], options: &Options) -> Result<Self, Error> { |
48 | 372 | Self::compile_with_options_includes(data, &CodecMap::default(), options) |
49 | 372 | } |
50 | | |
51 | 6 | pub fn compile_with_includes(data: &'fragment [u8], codecs: &CodecMap<'fragment, '_>) -> Result<Self, Error> { |
52 | 6 | Self::compile_with_options_includes(data, codecs, &Options::default()) |
53 | 6 | } |
54 | | |
55 | 378 | pub fn compile_with_options_includes(data: &'fragment [u8], codecs: &CodecMap<'fragment, '_>, options: &Options) -> Result<Self, Error> { |
56 | 378 | let mut fragments = HashMap::new(); |
57 | 378 | let mut frag_stack = Vec::new(); |
58 | 1.01M | let lines = data.split(|v| *v == b'\n')378 ; |
59 | 32.1k | for line31.7k in lines { |
60 | 31.7k | if line.is_empty() { Branch (60:16): [True: 259, False: 31.3k]
Branch (60:16): [True: 1, False: 176]
|
61 | 260 | continue; |
62 | 31.5k | } |
63 | 31.5k | let line = if line[line.len() - 1] == b'\r' { Branch (63:27): [True: 31.2k, False: 118]
Branch (63:27): [True: 176, False: 0]
|
64 | 31.4k | &line[..line.len() - 1] |
65 | | } else { |
66 | 118 | line |
67 | | }; |
68 | 31.5k | if line.is_empty() { Branch (68:16): [True: 1.82k, False: 29.5k]
Branch (68:16): [True: 19, False: 157]
|
69 | 1.84k | continue; |
70 | 29.6k | } |
71 | 29.6k | if line.starts_with(b"#include ") && frag_stack.is_empty()6 { Branch (71:16): [True: 6, False: 29.5k]
Branch (71:50): [True: 6, False: 0]
Branch (71:16): [True: 0, False: 157]
Branch (71:50): [True: 0, False: 0]
|
72 | 6 | let name = std::str::from_utf8(&line[9..]).map_err(|_| Error::InvalidUTF80 )?0 ; |
73 | 6 | let template = codecs.get(name).ok_or_else(|| Error::IncludeNotFound(name.into())0 )?0 ; |
74 | 36 | fragments.extend(template.fragments.iter().map(6 |(k, v)| (k.clone(), v.clone()))); |
75 | 29.6k | } else if line.starts_with(b"#fragment push ") { Branch (75:23): [True: 4.06k, False: 25.4k]
Branch (75:23): [True: 8, False: 149]
|
76 | 4.07k | let fragment = std::str::from_utf8(&line[15..]).map_err(|_| Error::InvalidUTF80 )?0 ; |
77 | 4.07k | if let Some(id129 ) = fragment.find(":") { Branch (77:24): [True: 129, False: 3.93k]
Branch (77:24): [True: 0, False: 8]
|
78 | 129 | let name = &fragment[..id]; |
79 | 129 | let mode = &fragment[id + 1..]; |
80 | 129 | frag_stack.push(Fragment { |
81 | 129 | name, |
82 | 129 | content: Vec::new(), |
83 | 129 | mode: FragmentMode::from_str(mode).ok_or_else(|| Error::UnknownFragmentMode(mode.into())0 )?0 , |
84 | | }); |
85 | 3.94k | } else { |
86 | 3.94k | frag_stack.push(Fragment { |
87 | 3.94k | name: fragment, |
88 | 3.94k | content: Vec::new(), |
89 | 3.94k | mode: FragmentMode::Default, |
90 | 3.94k | }); |
91 | 3.94k | } |
92 | 25.6k | } else if line.starts_with(b"#fragment pop") { Branch (92:23): [True: 4.06k, False: 21.3k]
Branch (92:23): [True: 8, False: 141]
|
93 | 4.07k | if frag_stack.is_empty() { Branch (93:20): [True: 0, False: 4.06k]
Branch (93:20): [True: 0, False: 8]
|
94 | 0 | return Err(Error::InvalidPop); |
95 | 4.07k | } |
96 | 7.69k | let combined_name = frag_stack.iter().map(|v| v.name).join("."); |
97 | 4.07k | //SAFETY: this is fine because the fragment stack must not be empty at this point. |
98 | 4.07k | let mut fragment = unsafe { frag_stack.pop().unwrap_unchecked() }; |
99 | 4.07k | if options.is_fragment_disabled(&combined_name) { Branch (99:20): [True: 4, False: 4.05k]
Branch (99:20): [True: 0, False: 8]
|
100 | 4 | fragment.content.clear(); |
101 | 4.06k | } |
102 | 4.07k | fragments.insert(combined_name, fragment); |
103 | | } else { |
104 | 21.5k | let cur_fragment = frag_stack.last_mut().ok_or(Error::NoFragment)?0 ; |
105 | 21.5k | let mut token = Token::new(line); |
106 | 817k | while token.has_next() { Branch (106:23): [True: 791k, False: 21.3k]
Branch (106:23): [True: 5.19k, False: 141]
|
107 | 796k | if token.cur() == b'{' { Branch (107:24): [True: 22.2k, False: 768k]
Branch (107:24): [True: 90, False: 5.10k]
|
108 | 22.3k | if token.next() == Some(b'{') { Branch (108:28): [True: 5.52k, False: 16.7k]
Branch (108:28): [True: 44, False: 46]
|
109 | 5.56k | token.inc(); |
110 | 16.7k | } |
111 | 22.3k | if let Some(component20.9k ) = token.pop()?0 .map(Component::Constant) { Branch (111:32): [True: 20.8k, False: 1.35k]
Branch (111:32): [True: 90, False: 0]
|
112 | 20.9k | cur_fragment.content.push(component); |
113 | 20.9k | }1.35k |
114 | 773k | } else if token.cur() == b'}' { Branch (114:31): [True: 22.2k, False: 746k]
Branch (114:31): [True: 90, False: 5.01k]
|
115 | 22.3k | if token.next() == Some(b'}') { Branch (115:28): [True: 5.52k, False: 16.7k]
Branch (115:28): [True: 44, False: 46]
|
116 | 5.56k | token.inc(); |
117 | 5.56k | if let Some(component) = token.pop()?0 .map(Component::Constant) { Branch (117:36): [True: 5.52k, False: 0]
Branch (117:36): [True: 44, False: 0]
|
118 | 5.56k | cur_fragment.content.push(component); |
119 | 5.56k | }0 |
120 | 16.7k | } else if let Some(component) = Branch (120:39): [True: 16.7k, False: 0]
Branch (120:39): [True: 46, False: 0]
|
121 | 16.7k | token.pop()?0 .map(|v| Component::parse_variable(options.functions(), v)) |
122 | | { |
123 | 16.7k | cur_fragment.content.push(component?0 ); |
124 | 0 | } |
125 | 751k | } |
126 | 796k | token.inc() |
127 | | } |
128 | 21.5k | if let Some(component9.75k ) = token.pop()?0 .map(Component::Constant) { Branch (128:24): [True: 9.69k, False: 11.7k]
Branch (128:24): [True: 63, False: 78]
|
129 | 9.75k | cur_fragment.content.push(component); |
130 | 11.7k | } |
131 | 21.5k | cur_fragment.content.push(Component::NewLine); |
132 | | } |
133 | | } |
134 | 378 | Ok(Template { |
135 | 378 | fragments, |
136 | 378 | variables: HashMap::new(), |
137 | 378 | }) |
138 | 378 | } |
139 | | |
140 | 1.13k | pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self { |
141 | 1.13k | self.variables.insert(key, value.into()); |
142 | 1.13k | self |
143 | 1.13k | } |
144 | | |
145 | 65 | pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self { |
146 | 65 | self.variables.insert(key, value.to_string().into()); |
147 | 65 | self |
148 | 65 | } |
149 | | |
150 | 2.37k | fn render_internal( |
151 | 2.37k | &self, |
152 | 2.37k | variables: &HashMap<&str, Cow<str>>, |
153 | 2.37k | path: &str, |
154 | 2.37k | fragments: &[&str], |
155 | 2.37k | ) -> Result<String, Error> { |
156 | 2.37k | let mut rendered = Vec::new(); |
157 | 4.97k | for name2.60k in fragments { |
158 | 2.60k | let name: Cow<str> = match path.is_empty() { |
159 | 1.68k | false => Cow::Owned(format!("{}.{}", path, name)), |
160 | 924 | true => Cow::Borrowed(name), |
161 | | }; |
162 | 2.60k | let fragment = self.fragments.get(&*name).ok_or_else(|| Error::FragmentNotFound(String::from(&*name))0 )?0 ; |
163 | 2.60k | let sub_rendered = fragment |
164 | 2.60k | .content |
165 | 2.60k | .iter() |
166 | 48.7k | .map(|v| match v { |
167 | 23.2k | Component::Constant(v) => Ok(Cow::Borrowed(*v)), |
168 | 9.87k | Component::Variable(v) => { |
169 | 9.87k | let variable = variables.get(v.name).ok_or_else(|| Error::VariableNotFound(v.name.into())0 )?0 ; |
170 | 9.87k | if let Some(function129 ) = v.function { Branch (170:32): [True: 129, False: 9.74k]
Branch (170:32): [True: 0, False: 4]
|
171 | 129 | let variable = function(variable); |
172 | 129 | Ok(variable) |
173 | | } else { |
174 | 9.74k | Ok(Cow::Borrowed(&**variable)) |
175 | | } |
176 | | } |
177 | 15.6k | Component::NewLine => Ok(Cow::Borrowed("\n")), |
178 | 48.7k | }) |
179 | 2.60k | .collect::<Result<Vec<Cow<str>>, Error>>()?0 |
180 | 2.60k | .join(""); |
181 | 2.60k | if fragment.mode == FragmentMode::Inline { Branch (181:16): [True: 148, False: 2.45k]
Branch (181:16): [True: 0, False: 1]
|
182 | 148 | rendered.push(sub_rendered.trim().into()); |
183 | 2.45k | } else { |
184 | 2.45k | rendered.push(sub_rendered); |
185 | 2.45k | } |
186 | | } |
187 | 2.37k | Ok(rendered.join("")) |
188 | 2.37k | } |
189 | | |
190 | 1.58k | pub fn scope(&self) -> Scope { |
191 | 1.58k | Scope { |
192 | 1.58k | template: self, |
193 | 1.58k | variables: self.variables.clone(), |
194 | 1.58k | } |
195 | 1.58k | } |
196 | | |
197 | 465 | pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> { |
198 | 465 | self.render_internal(&self.variables, path, fragments) |
199 | 465 | } |
200 | | } |
201 | | |
202 | | #[derive(Clone)] |
203 | | pub struct Scope<'a, 'fragment, 'variable> { |
204 | | template: &'a Template<'fragment, 'variable>, |
205 | | variables: HashMap<&'variable str, Cow<'variable, str>>, |
206 | | } |
207 | | |
208 | | impl<'variable> Scope<'_, '_, 'variable> { |
209 | 4.35k | pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self { |
210 | 4.35k | self.variables.insert(key, value.into()); |
211 | 4.35k | self |
212 | 4.35k | } |
213 | | |
214 | 3.19k | pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self { |
215 | 3.19k | self.variables.insert(key, value.to_string().into()); |
216 | 3.19k | self |
217 | 3.19k | } |
218 | | |
219 | 409 | pub fn render_to_var(&mut self, path: &str, fragments: &[&str], key: &'variable str) -> Result<&mut Self, Error> { |
220 | 409 | let str = self.template.render_internal(&self.variables, path, fragments)?0 ; |
221 | 409 | self.variables.insert(key, str.into()); |
222 | 409 | Ok(self) |
223 | 409 | } |
224 | | |
225 | 1.49k | pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> { |
226 | 1.49k | self.template.render_internal(&self.variables, path, fragments) |
227 | 1.49k | } |
228 | | } |